package com.functest.util

import org.slf4j.Logger
import org.slf4j.LoggerFactory

import java.util.concurrent.TimeUnit

/**
 * @author: DavydovMD
 * Date: 22.03.13
 * Time: 11:00
 */

class Retrier {
    protected boolean doLog = true
    protected String desc = 'Some action'
    protected final Logger logger = LoggerFactory.getLogger(Retrier)

    private List<Closure<Boolean>> expirationChecks = [{ false }]
    private Integer attemptsLimit = null;//By default - loop forever
    private int attemptCounter = 0
    private int pollingIntervalMillis = 100

    long getCurrentTime() {
        System.currentTimeMillis()
    }

    void sleep(int pollingIntervalMillis) {
        super.sleep pollingIntervalMillis
    }


    Retrier withAttempts(int numAttempts, int pollingInterval = pollingIntervalMillis, TimeUnit unit = TimeUnit.MILLISECONDS) {
        attemptsLimit = numAttempts
        attemptCounter = 0
        withInterval(pollingInterval, unit)
    }

    protected logFailure = { failureDesc ->
        if (doLog) {
            logger.warn("Retrier: Action [$desc] failed:$failureDesc.")
        }
    }

    Retrier withLogging(String description) {
        doLog = true
        desc = description
        this
    }

    Retrier withoutLogging() {
        doLog = false
        this
    }

    Retrier withInterval(int interval, TimeUnit unit = TimeUnit.MILLISECONDS) {
        pollingIntervalMillis = OrmUtil.toMillis(interval, unit)
        this
    }

    Retrier withTimeout(int timeout, TimeUnit unit = TimeUnit.MILLISECONDS) {
        int timeoutMillis = OrmUtil.toMillis(timeout, unit)
        long startTime = currentTime
        expirationChecks << {
            if (currentTime > startTime + timeoutMillis) {
                logger.trace("${currentTime} > ${startTime} + ${timeoutMillis} ")
                logFailure(" Timeout of ${timeout} ${unit} exceeded")
                return true
            }
        }
        this
    }

    boolean exec(Closure tryClosure) {
        while (attemptsLimit ? attemptCounter <= attemptsLimit : true) {
            if (tryClosure()) return true
            for (Closure check in expirationChecks) {
                if (check()) return false
            }
            sleep(pollingIntervalMillis)
            attemptCounter++
        }
        if (attemptsLimit && attemptCounter > attemptsLimit) {
            logFailure(" number of attempts exceeded:${attemptCounter}")
            return false
        }
        true
    }
}
